﻿// <copyright file="TTableStorageQueryProvider.cs" company="N/A">
//     Copyright (c) Nicholas Barrett and Benjamin Kasten.  All rights reserved.
//
//     This program is free software; you can redistribute it and/or modify it
//     under the terms of the GNU General Public License as published by the
//     Free Software Foundation; either version 2 of the License, or (at your
//     option) any later version.
//
//     This program is distributed in the hope that it will be useful, but
//     WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
//     Public License for more details.
//
//     You should have received a copy of the GNU General Public License along
//     with this program; if not, write to the Free Software Foundation, 
//     Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// </copyright>

namespace BetterAzureTableStorage
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Text;
    using Microsoft.WindowsAzure;

    /// <summary>
    /// Query provider for TTableStorageQuery
    /// </summary>
    /// <remarks>
    /// Add more information when you have it!
    /// </remarks>
    public class TTableStorageQueryProvider : IQueryProvider
    {
        /// <summary>
        /// Backing field for RenameProperty
        /// </summary>
        private Func<PropertyInfo, string> fRenameProperty;

        /// <summary>
        /// Gets or sets the rename function property.
        /// </summary>
        /// <value>
        /// The rename property.
        /// </value>
        public Func<PropertyInfo, string> RenameProperty
        {
            get
            {
                if (this.fRenameProperty == null)
                {
                    this.fRenameProperty = (t) => t.Name;
                }

                return this.fRenameProperty;
            }

            set
            {
                if (value != null)
                {
                    this.fRenameProperty = value;
                }
            }
        }

        private TTableStorageInfo TableInfo { get; set; }

        /// <summary>
        /// Gets or sets an action to create a TableStorageEngine
        /// </summary>
        private Func<string, TTableStorageEngine> CreateTableStorageEngine { get; set; }

        private TTableStorageDataContext Context { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="TTableStorageQueryProvider"/> class.
        /// </summary>
        /// <param name="tableName">Name of the table.</param>
        /// <param name="createTableStorageEngine">The create table storage engine action.</param>
        public TTableStorageQueryProvider(TTableStorageInfo tableInfo, Func<string, TTableStorageEngine> createTableStorageEngine, TTableStorageDataContext context)
        {
            this.TableInfo = tableInfo;
            this.CreateTableStorageEngine = createTableStorageEngine;
            this.Context = context;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TTableStorageQueryProvider"/> class.
        /// </summary>
        /// <param name="tableName">Name of the table.</param>
        /// <param name="createTableStorageEngine">The create table storage engine action.</param>
        /// <param name="renamePropertyFunc">The rename property function.</param>
        public TTableStorageQueryProvider(TTableStorageInfo tableInfo, Func<string, TTableStorageEngine> createTableStorageEngine, Func<PropertyInfo, string> renamePropertyFunc, TTableStorageDataContext context)
        {
            this.TableInfo = tableInfo;
            this.CreateTableStorageEngine = createTableStorageEngine;
            this.RenameProperty = renamePropertyFunc;
            this.Context = context;
        }

        /// <summary>
        /// Creates the query.
        /// </summary>
        /// <typeparam name="ElementT">The generic type of the return object.</typeparam>
        /// <param name="expression">The Linq expression tree that describes the query</param>
        /// <returns>
        /// An <see cref="T:System.Linq.IQueryable"/> that can evaluate the query represented by the specified expression tree.
        /// </returns>
        public IQueryable<ElementT> CreateQuery<ElementT>(Expression expression)
        {
            TTableStorageQuery<ElementT> qry = new TTableStorageQuery<ElementT>(this, expression, this.RenameProperty);
            return qry;
        }

        /// <summary>
        /// Constructs an <see cref="T:System.Linq.IQueryable"/> object that can evaluate the query represented by a specified expression tree.
        /// </summary>
        /// <param name="expression">An expression tree that represents a LINQ query.</param>
        /// <returns>
        /// An <see cref="T:System.Linq.IQueryable"/> that can evaluate the query represented by the specified expression tree.
        /// </returns>
        public IQueryable CreateQuery(Expression expression)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Executes the specified expression.
        /// </summary>
        /// <typeparam name="ResultT">The type of the result.</typeparam>
        /// <param name="expression">The expression.</param>
        /// <returns>The value that results from executing the specified query.</returns>
        public ResultT Execute<ResultT>(Expression expression)
        {
            TTableStorageQueryTranslator translator = this.CreateTranslator(expression);
            
            TTableStorageEngine engine = this.CreateTableStorageEngine(this.TableInfo.TableName);
            
            if (typeof(IEnumerable).IsAssignableFrom(typeof(ResultT)))
            {
                MethodInfo method = engine.GetType().GetMethod("GetEntities");
                MethodInfo genericMethod = method.MakeGenericMethod(typeof(ResultT).GetGenericArguments().FirstOrDefault());
                return (ResultT)genericMethod.Invoke(engine, new object[] { translator, null, 0, this.Context });
            }
            else
            {
                ResultT result = ((IEnumerable<ResultT>)engine.GetEntities<ResultT>(translator, null, 0, this.Context)).AsEnumerable<ResultT>().FirstOrDefault();
                return result;
            }
        }

        /// <summary>
        /// Executes the query represented by a specified expression tree.
        /// </summary>
        /// <param name="expression">An expression tree that represents a LINQ query.</param>
        /// <returns>
        /// The value that results from executing the specified query.
        /// </returns>
        public object Execute(Expression expression)
        {
            return this.Execute<IEnumerable<object>>(expression);
        }

        private TTableStorageQueryTranslator CreateTranslator(Expression expression)
        {
            TTableStorageQueryTranslator translator = new TTableStorageQueryTranslator();

            translator.Translate(expression, this.RenameProperty, this.TableInfo);

            return translator;
        }
    }
}
